﻿using Milan;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;

namespace Milan.Windows
{
	public class dbghelp
	{
		[Flags]
		public enum MiniDumpType : uint	// From dbghelp.h:
		{
			Normal = 0x00000000, WithDataSegs = 0x00000001,	WithFullMemory = 0x00000002, WithHandleData = 0x00000004, FilterMemory = 0x00000008,
			ScanMemory = 0x00000010, WithUnloadedModules = 0x00000020, WithIndirectlyReferencedMemory = 0x00000040, FilterModulePaths = 0x00000080,
			WithProcessThreadData = 0x00000100, WithPrivateReadWriteMemory = 0x00000200, WithoutOptionalData = 0x00000400, WithFullMemoryInfo = 0x00000800,
			WithThreadInfo = 0x00001000, WithCodeSegs = 0x00002000,	WithoutAuxiliaryState = 0x00004000,	WithFullAuxiliaryState = 0x00008000,
			WithPrivateWriteCopyMemory = 0x00010000, IgnoreInaccessibleMemory = 0x00020000, ValidTypeFlags = 0x0003ffff,
		};

		//typedef struct _MINIDUMP_EXCEPTION_INFORMATION {
		//    DWORD ThreadId;
		//    PEXCEPTION_POINTERS ExceptionPointers;
		//    BOOL ClientPointers;
		//} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION;
		[StructLayout(LayoutKind.Sequential, Pack = 4)]  // Pack=4 is important! So it works also for x64!
		struct MiniDumpExceptionInformation
		{
			public uint ThreadId;
			public IntPtr ExceptionPointers;
			[MarshalAs(UnmanagedType.Bool)]
			public bool ClientPointers;
		}

		//BOOL WINAPI MiniDumpWriteDump( __in HANDLE hProcess, __in DWORD ProcessId, __in HANDLE hFile, __in MINIDUMP_TYPE DumpType,
		//    __in_opt PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, __in_opt PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
		//    __in_opt PMINIDUMP_CALLBACK_INFORMATION CallbackParam );
		[DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
		static extern bool MiniDumpWriteDump( IntPtr hProcess, uint processId, IntPtr hFile, uint dumpType,
			ref MiniDumpExceptionInformation expParam, IntPtr userStreamParam, IntPtr callbackParam);

		/// <summary>
		/// Helper function to MiniWriteDump to just dump with full memory
		/// </summary>
		/// <param name="fileName">Where to dump the stack</param>
		/// <returns></returns>
		public static bool MiniDumpWriteDump(string fileName)
		{
			return MiniDumpWriteDump(fileName, MiniDumpType.WithFullMemory|MiniDumpType.IgnoreInaccessibleMemory);
		}

		/// <summary>
		/// Helper function to MiniDumpWriteDump
		/// </summary>
		/// <param name="fileName"></param>
		/// <param name="dumpTyp"></param>
		/// <returns></returns>
		public static bool MiniDumpWriteDump(string fileName, MiniDumpType dumpTyp)
		{
			using (var fs = new System.IO.FileStream(fileName, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None))
			{
				MiniDumpExceptionInformation exp;
				exp.ThreadId = kernel32.GetCurrentThreadId();
				exp.ClientPointers = false;
				exp.ExceptionPointers = System.Runtime.InteropServices.Marshal.GetExceptionPointers();
				bool bRet = MiniDumpWriteDump( kernel32.GetCurrentProcess(), kernel32.GetCurrentProcessId(),
					fs.SafeFileHandle.DangerousGetHandle(), (uint)dumpTyp, ref exp, IntPtr.Zero, IntPtr.Zero);
				return bRet;
			}
		}

		/// <summary>
		/// Helper function to MiniDumpWriteDump of the current exception in this module.
		/// </summary>
		/// <param name="fileName"></param>
		/// <param name="dumpTyp"></param>
		/// <returns>returns the name of the dump file</returns>
		public static string MiniDumpWriteDumpCurrent(IntPtr pExceptionPointers)
		{
			Process process = Process.GetCurrentProcess();
			DateTime linkerTime = Diagnostics.Pe.RetrieveLinkerTimestamp(process.MainModule.FileName);
			MiniDumpExceptionInformation exp;
			exp.ThreadId = kernel32.GetCurrentThreadId();
			exp.ClientPointers = false;
			if (pExceptionPointers == IntPtr.Zero)
				exp.ExceptionPointers = System.Runtime.InteropServices.Marshal.GetExceptionPointers();
			else
				exp.ExceptionPointers = pExceptionPointers;
			string fileName = Path.GetFileNameWithoutExtension(process.ProcessName) + "_" + linkerTime.ToString("yy_MM_dd_HH_mm_ss") + ".dmp";
			string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "asim", "minidump", fileName);
			using (var fs = new System.IO.FileStream(path, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None))
			{
				MiniDumpWriteDump(kernel32.GetCurrentProcess(), kernel32.GetCurrentProcessId(),
					fs.SafeFileHandle.DangerousGetHandle(), (uint)(MiniDumpType.WithFullMemory | MiniDumpType.IgnoreInaccessibleMemory), ref exp, IntPtr.Zero, IntPtr.Zero);
			}
			return path;
		}

		/// <summary>
		/// All un-handled exceptions are dumped to the correct directory
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private static void UnhandledThreadHandler(object sender, ThreadExceptionEventArgs e)
		{
			MiniDumpWriteDumpCurrent(IntPtr.Zero);
		}
	}
}
